Skip to content

Recognise PersistentBase and XPBaseObject as persistent bases - #10

Merged
peopleworks merged 2 commits into
peopleworks:mainfrom
MBrekhof:fix/xpo-root-base-types
Aug 13, 2026
Merged

Recognise PersistentBase and XPBaseObject as persistent bases#10
peopleworks merged 2 commits into
peopleworks:mainfrom
MBrekhof:fix/xpo-root-base-types

Conversation

@MBrekhof

Copy link
Copy Markdown

What this changes

PersistentBase and XPBaseObject join BaseTypeNames, and the list stops being duplicated across the CLI, the MCP server and the test harness.

Why

I said in #6 this was one word. It is two, and the second one is the interesting one.

The XPO hierarchy, per the class comparison page:

PersistentBase → XPBaseObject → XPCustomObject → XPObject
                              → XPLiteObject

The list held XPCustomObject, XPObject and XPLiteObject — the three leaves — and neither of the two classes above them. So the hole was in the middle of a documented API rather than at its edge: DevExpress names all five as bases a persistent class may derive from, and the same page says "We recommend that you use the PersistentBase class as the base class for persistent classes in your applications."

It is also not an exotic corner. XPBaseObject and PersistentBase carry no built-in key, which is exactly why you pick them when the table already has one — the same population of legacy schemas the DbSet roster was added for in #3. That is presumably why this surfaced beside it.

The duplication, which is the part I got wrong before

I claimed on #4 that BaseTypeNames defaults to four names "the CLI never uses" and that ApplicationUser was therefore missed in the field. That was wrong and I corrected it — but the thing underneath it is real, just in the other direction: the list exists in four places.

Where Names
ExtractionOptions default 4
Cli/Program.cs 5
Mcp/XafProjectContext.cs 5
SampleProjects.Extract 5

Three copies agreed with each other and the default disagreed with all of them, which is what made me misread it. Four copies is three chances for them to drift, and the drift is silent — each one is just a different opinion about what an entity is. The callers now use the default, so the change is -3 duplicated arrays and one list to edit next time.

If you would rather keep the call sites explicit, say so and I will put the two names in all four instead; the fix works either way and I do not want to smuggle a refactor into a bug fix.

Verification

Corpus before after
FeatureCenter.NET.XPO 43 46
MainDemo.NET.XPO 14 14
MainDemo.NET.EFCore 14 14
OutlookInspiredDemo.NET.EFCore 23 23
WLNCentral (private, 221 entities) 196 196

The three FeatureCenter additions are OidGenerator, NoKeyPropertyNamedBaseObject and LayoutDemoObject, all : XPBaseObject — the last from the FeatureCenter.Module.Win sibling project.

No demo derives from PersistentBase directly, so that half is covered by the fixture and not by a corpus. Worth knowing rather than implying both halves were proven in the field.

Numbers are against main, so they do not include #9; the two changes are independent and address different causes from #6.

Three existing assertions moved, deliberately

The XPO fixture gained AuditEntry : XPBaseObject and SequenceCounter : PersistentBase, because the canonical XPO sample having no class on either base is why the hole was invisible. That changes three counts, and I would rather point at them than have them found:

  • ExtractionTests.FindsEveryEntity — expected set now names all five
  • HtmlExplainerTests.GraphPlacesEveryEntity35 nodes
  • McpToolTests.AbsentEntityReturnsTheCompleteInventoryAndSaysItDoesNotExist"complete list of 3 entities"5

Each is a count of what the fixture holds, and each still asserts exactly what it asserted before. If you would rather the fixture stayed at three entities, the two classes can move to their own solution folder instead.

280 passed, 0 failed, dotnet build XAFLogicExplainer.slnx clean at 0 warnings. The new test fails on main for the right reason.

Fixes the second of the three causes in #6.

Checklist

  • dotnet build XAFLogicExplainer.slnx is clean (CI treats warnings as errors)
  • No DevExpress reference was added to XafLogicExplainer.Core
  • Extraction still works on a project that does not compile
  • An unrecognized variation is skipped, not thrown on — an unknown base name is simply not a match
  • CHANGELOG.md updated under [Unreleased]

peopleworks
peopleworks previously approved these changes Aug 13, 2026

@peopleworks peopleworks left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, and this one taught me something about my own issue.

I filed #7 as a duplication problem: one list written in four places, plus a default that agreed with none of them. You fixed that — the four copies are gone and callers now get the default — and then noticed the thing I had not, which is that the list itself was wrong in the middle.

PersistentBase → XPBaseObject → XPCustomObject → XPObject
                              → XPLiteObject

We recognised the leaves and not the two classes above them. I had looked straight at that array while writing #7 and read it as a set of names to deduplicate rather than as a hierarchy with a hole in it. Your framing is the one that makes it obvious:

mapping onto a table that brings its own key is exactly when the higher bases are the right choice

Which is the same population every fix this week has been about. An application on an existing schema is precisely the one that will derive from XPBaseObject or PersistentBase, and it was the one we could not see.

The two fixtures are the right two: AuditEntry : XPBaseObject supplying its own [Key] because the base carries none, and SequenceCounter : PersistentBase on a natural string key. Both are shapes a real legacy module has, not shapes invented to make a test pass.

Verified in a clean worktree, Windows, .NET 10, Release:

Passed!  Failed: 0, Passed: 280, Skipped: 0     0 warnings, 0 errors

I checked the over-reporting direction too, since widening a seed list is exactly where that risk lives, and more so once #9's base walk propagates from these roots. Every one of the five names is documented by DevExpress as a base a persistent class may derive from, so anything reaching them is persistent by definition. No note needed — I am recording that I looked.

Closes #7. Thank you for taking an issue that was mine to fix.

One practical thing

You and #9 both edit README.md, CHANGELOG.md and SampleProjects.cs, and you claim different test counts — 280 here, 283 there. Whichever merges second will conflict and then fail CountsTheTestsThatActuallyExist, which reads the README claim against the real suite. Not a problem with either change, just an order-of-merge tax: rebase the second and fix the number.

If you have a preference, merge this one first — it is the smaller rebase, and #9's base walk is more interesting to read against a seed list that is already correct.

@peopleworks

Copy link
Copy Markdown
Owner

Rebased onto main and force-pushed to your branch — maintainerCanModify was on, and this was mechanical rather than yours to spend time on. Your authorship is intact on both commits; only the committer line is mine.

What I resolved:

  • CHANGELOG.md — three pull requests all adding their entry at the same spot. Your ### Changed section moved above ### Fixed, which is the Keep a Changelog order and what 0.12.0 does.
  • README.md — the test-count claim. main was at 285; this branch makes it 286.
Passed!  Failed: 0, Passed: 286, Skipped: 0     0 warnings, 0 errors

Nothing of yours changed. Shout if you would rather have done it yourself and I will keep my hands off the next one.

One thing you should know before it surprises you: force-pushing dismissed my approval, because main's protection has dismiss_stale_reviews on. That is the setting working — a review approves a diff, not a branch name — but it means both of these need re-approving now.

Brekhof and others added 2 commits August 13, 2026 18:55
The XPO hierarchy is PersistentBase -> XPBaseObject -> XPCustomObject ->
XPObject, with XPLiteObject also under XPBaseObject. BaseTypeNames held the
three leaves and neither of the classes above them, so the hole sat in the
middle of an API that is used rather than at its edge: DevExpress documents all
five as bases a persistent class may derive from, and recommends PersistentBase.

Deriving from the higher bases is what you do when the table already brings its
own key -- the same population of legacy schemas the DbSet roster was added for,
which is why this turned up beside it.

FeatureCenter.NET.XPO gains OidGenerator, NoKeyPropertyNamedBaseObject and
LayoutDemoObject, all of them ": XPBaseObject". Nothing changes for MainDemo in
either ORM, for OutlookInspired, or for a private 221-entity EF Core
application. No demo derives from PersistentBase directly, so that half is
covered by the fixture rather than by a corpus.

The list also stops being duplicated. The CLI, the MCP server and the test
harness each passed their own copy, so the default in Core was four names while
every caller passed five -- four copies and three chances for them to disagree
about what an entity is. They now use the default.

Fixes peopleworks#6 in part; the transitive walk in peopleworks#9 is the rest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fifth copy, found by peopleworks in peopleworks#7. Leaving it would have kept the sync path on the old list while every other caller moved, which is the same divergence one copy further along.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@peopleworks
peopleworks force-pushed the fix/xpo-root-base-types branch from 1db064c to 826cea0 Compare August 13, 2026 22:56
@peopleworks
peopleworks merged commit 0e9d760 into peopleworks:main Aug 13, 2026
6 checks passed
@MBrekhof
MBrekhof deleted the fix/xpo-root-base-types branch August 18, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants